/*
 * This program is free software: you can redistribute it and/or
 * modify it under the terms of the GNU General Public License as
 * published by the Free Software Foundation, version 3 of the
 * License.
 *
 * This program is distributed WITHOUT ANY WARRANTY; without even the
 * implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR
 * PURPOSE. See the
 *
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program. If not, see <http://www.gnu.org/licenses/>
 */

#include <err.h>
#include <fcntl.h>
#include <stdarg.h>
#include <stdint.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <time.h>
#include <unistd.h>

#include "arg.h"
#include "config.h"
#include "util.h"

char *argv0;

static const char *formats[] = { "%F %R", "%F", "%m-%d", "%R", NULL };

static void
get_date(struct tm *dst, const char *s)
{
	struct tm tm;
	const char **format;

	tm = *dst;
	for (format = formats; format[0]; format++) {
		tm = *dst;
		if (strptime(s, format[0], &tm))
			break;
	}

	if (!format[0])
		errx(3, "strptime: no such format");

	*dst = tm;
}

static struct tm
get_localtime(void)
{
	time_t t;

	t = time(NULL);
	return *localtime(&t);
}

__attribute__((__format__(printf, 3, 4))) static unsigned int
esnprintf(char *str, size_t size, const char *format, ...)
{
	int s;
	va_list ap;

	va_start(ap, format);
	s = vsnprintf(str, size, format, ap);
	va_end(ap);
	if (s < 0 || (size_t)s >= size)
		err(2, "vsnprintf");
	return (unsigned int)s;
}

static void
esmwfmt(char *s)
{
	if (!s[0] ||
	    (s[0] != '-' && s[0] != '+' && s[0] != '=' && s[0] != '_'))
		errx(2, "unknown balance value format");
	if (s[0] == '_')
		s[0] = '=';
	if (s[0] == '=')
		fputs("not recommented to break balance history\n", stderr);
}

static size_t
ewrite(int fd, const void *buf, size_t nbytes)
{
	ssize_t s;
	const char *tmp;

	tmp = buf;
	for (s = 0; nbytes;) {
		s = write(fd, tmp, nbytes);
		if (s <= 0)
			err(3, "write");
		tmp += s;
		nbytes -= (size_t)s;
	}

	if (buf > (const void *)tmp)
		errx(3, "pointer overflow");

	return (size_t)(tmp - (const char *)buf);
}

static char *
rea_str_delim_cat(char *s1, const char *s2)
{
	size_t s1len;
	size_t s2len;

	if (s1)
		s1len = estrnlen(s1, SIZE_MAX);
	else
		s1len = 0;
	s2len = estrnlen(s2, SIZE_MAX);

	if (s1len)
		s1len += sizeof((char)' ');
	s1 = realloc(s1, s1len + s2len + sizeof((char)'\0'));
	if (!s1)
		err(2, "realloc");
	if (s1len)
		s1[s1len - 1] = ' ';
	memcpy(&s1[s1len], s2, s2len + sizeof((char)'\0'));

	return s1;
}

__attribute__((noreturn)) static void
usage(void)
{
	dprintf(STDERR_FILENO,
	        "usage: %s [-v] [-d date] [-f path]"
	        " <-+=>value [category] [description]\n",
	        argv0);
	exit(1);
}

int
main(int argc, char **argv)
{
	static const char *file = DEFAULT_PATH;
	static const char *v    = NULL;
	static const char *c    = NULL;
	static char *d          = NULL;

	static struct tm tm;
	static char buf[STRLEN + sizeof((char)'\0')];

	int fd;
	size_t l;
	int waitforflags;

	memset(&tm, '\x00', sizeof(tm));
	tm        = get_localtime();
	tm.tm_sec = 0;

	waitforflags = 1;
	ARGBEGIN {
		case 'd':
			SHIFT_ARG_IF_DOESNT_EXIST errx(1, "usage: -d date");
			get_date(&tm, argv[0]);
			goto next;
		case 'f':
			SHIFT_ARG_IF_DOESNT_EXIST errx(1, "usage: -f path");
			file = argv[0];
			goto next;
		case 'v':
			WRITES(STDOUT_FILENO, "smw-" VERSION"\n");
			return 0;
		case NUMARG:
			argv[0]--;
			esmwfmt(argv[0]);
			v            = argv[0];
			waitforflags = 0;
			goto next;
		default:
			if (!waitforflags) {
				argv[0]--;
				goto skipflags;
			}
			usage();
	} NOFLAGARG {
		waitforflags = 0;
	skipflags:
		if (!v) {
			esmwfmt(argv[0]);
			v = argv[0];
		} else if (!c) {
			c = argv[0];
		} else if (!d) {
			d = rea_str_delim_cat(NULL, argv[0]);
		} else {
			d = rea_str_delim_cat(d, argv[0]);
		}
	} ARGEND

	if (!v)
		usage();

	fd = open(file, O_WRONLY | O_APPEND | O_CREAT, 0666);
	if (fd < 0)
		err(2, "open");

	l = strftime(buf, sizeof(buf), "%F %R", &tm);
	l += esnprintf(buf + l, sizeof(buf) - l, "\t%s\t%s\t%s\n", v,
	               c ? c : "", d ? d : "");
	if (d)
		free(d);
	ewrite(fd, buf, l);

	return 0;
}
